/* httpConnectionHandler.c */
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <stdio.h>
#include <time.h>
#include "httpConnectionHandler.h"
#include "telnetConnectionHandler.h"
#include "fileHandler.h"
#include "Logger.h"
#include "socketHelper.h"
#include "configFile.h"
#include "utilities.h"

/* Defines */
#define BUFFER_SIZE 512
#define SERVER_NAME "best-server1.0"
#define RECIEVE_OK 1

/* Prototypes */
static char* getHttpResponseFromCode(int statusCode);
static responseHttpHeaderT generateHttpHeader(httpConnectionT *conn, fileInfoT* fileInfo);
static void getCurrentTime(httpConnectionT *conn);
static void sendHttpHeader(httpConnectionT *conn, responseHttpHeaderT *respHeader, int size);
static void closeHttpConnection(httpConnectionT *connection);
static void saveToTextFile(httpConnectionT *conn);
static int connectionClosedCallback(int id);
static void addHttpHeaderToMsg(char* dest, char* source);
static void handleConditionalGet(httpConnectionT *conn, char* date);
static int httpRecieveLoop(httpConnectionT* conn);

static short connectionStatus[MAX_CONNECTIONS];
static HANDLE httpThreadHandle;
static int httpThreadId;
static SOCKET listenSocket;
static short shouldListen;

/* Function: initNewHttpConnection
* Usage: newHttpConnection = initNewHttpConnection();
* -------------------------------- 
* Initializes a new http connection. 
*/
httpConnectionT initNewHttpConnection() {
	httpConnectionT newConn;

	newConn.buffer = initHttpBuffer();
	newConn.clientSocket = INVALID_SOCKET;
	strcpy(newConn.protocolVersion, "UNKNOWN");
	strcpy(newConn.requestedFile, "UNKNOWN");
	strcpy(newConn.dateTime, "UNKNOWN");
	strcpy(newConn.httpAction, "UNKNOWN");
	strcpy(newConn.getConditionalDate, "NONE");
	newConn.statusCode = 0;
	newConn.bufferSize = 0;

	return newConn;
}

/* Function: getHttpStatus
* Usage: getHttpStatus();
* -------------------------------- 
* Checks if the server is open to connect to or not.
*/
short getHttpStatus() {
	return shouldListen;
}

/* Function: startHttpServer
* Usage: startHttpServer();
* -------------------------------- 
* Starts the http server.
*/
void startHttpServer() {
	printf("Creating http thread.\n");
	shouldListen = HTTP_LISTEN_ON;
	httpThreadHandle = CreateThread(NULL, 0, httpListenLoop, NULL, 0, &httpThreadId);
}

/* Function: resumeHttpServer
* Usage: resumeHttpServer(conn);
* -------------------------------- 
* Resumes the http server if it has been stopped. 
*/
void resumeHttpServer(telnetConnectionT *conn) {
	if (shouldListen == HTTP_LISTEN_OFF) {
		send(conn->clientSocket, "Starting HTTP server..\r\n", sizeof("Starting HTTP server..\r\n"), 0);
		startHttpServer();
		send(conn->clientSocket, "The HTTP server is up and running.\r\n", sizeof("The HTTP server is up and running.\r\n"), 0);
	}
	else {
		printf("Server already running.\n");
		send(conn->clientSocket, "The HTTP server is already running.\r\n", sizeof ("The HTTP server is already running.\r\n"), 0);
	}
}

/* Function: stopHttpServer 
* Usage: stopHttpServer(conn);
* -------------------------------- 
*  Stops the traffic to the http server.
*/
void stopHttpServer(telnetConnectionT *conn) {
	if (shouldListen == HTTP_LISTEN_ON){
		printf("Shutting down http listen socket.\n");
		send(conn->clientSocket, "Shutting down http listen socket.\r\n", sizeof("Shutting down http listen socket.\r\n"), 0);
		shutdown(listenSocket, SD_BOTH);
		
		printf("Closing http listen socket.\n");
		send(conn->clientSocket, "Closing http listen socket.\r\n", sizeof("Closing http listen socket.\r\n"), 0);
		closesocket(listenSocket);
		
		shouldListen = HTTP_LISTEN_OFF;
	}
	else {
		printf("Server is already terminated.\n");
		send(conn->clientSocket, "The server is already down.\r\n", sizeof("The server is already down.\r\n"), 0);
	}
}

/* Function: connectionClosedCallback
* Usage: connectionClosedCallback(connection->connectionId);
* -------------------------------- 
* Frees up the memory from a connection when it's closed.
*/
static int connectionClosedCallback(int id) {
	connectionStatus[id] = 0;
	return 0;
}

/* Function: httpListenLoop
* Usage: httpListenLoop();
* -------------------------------- 
* A loop that listens for new connections.
*/
DWORD WINAPI httpListenLoop() {
	int clientInfoLength, numConnections = 0, i = 0, errorCode, result;
	SOCKADDR_IN listenInfo;
	httpConnectionT newHttpConnection, connectionArray[MAX_CONNECTIONS];
	LPDWORD tIds[MAX_CONNECTIONS];
	HANDLE clientThreadHandles[MAX_CONNECTIONS];

	memset(connectionStatus, 0, MAX_CONNECTIONS);
	/* Initialize listen socket*/
	result = initListenSocket(&listenInfo, &listenSocket, getHttpPort());
	if(result == SOCKET_ERROR) {
		printf("Startup failed! \n");
		return 0;
	}
	clientInfoLength = sizeof(newHttpConnection.clientInfo);

	while (1) {
		if (shouldListen == HTTP_LISTEN_ON) {
			newHttpConnection = initNewHttpConnection();
			printf("Waiting for new HTTP connection...\n");
			newHttpConnection.clientSocket = accept(listenSocket, (SOCKADDR *)&newHttpConnection.clientInfo, &clientInfoLength);

			if (newHttpConnection.clientSocket == SOCKET_ERROR) {
				errorCode = WSAGetLastError();
				if (errorCode == WSAEINTR)
					printf("Listensocket accept block interupted.\n");
				else
					printf("Listensocket error..[%d]\n", errorCode);
				break;
			}
			for (i = 0; i < MAX_CONNECTIONS; i++) {
				if (connectionStatus[i] == 0) {
					connectionStatus[i] = 1;
					newHttpConnection.connectionId = i;
					connectionArray[i] = newHttpConnection;
					break;
				}
			}

			if (i >= MAX_CONNECTIONS) {
				printf("Max connections reached, discarding connection.\n");
			}
			else {
				printf("Accepted connection.\n");

				clientThreadHandles[i] = CreateThread(
					NULL,
					0,
					handleHttpConnection,
					&connectionArray[i],
					0,
					&tIds[i]);

				Sleep(10);
			}
		}
		else {
			break;
		}
	}
	printf("Waiting for client threads to terminate.\n");
	WaitForMultipleObjects(i, clientThreadHandles, TRUE, 10000);
	printf("HTTP Thread terminating!\n");
	return 0;
}

/* Function: parseHttpHeader
* Usage: parseHttpHeader(conn);
* -------------------------------- 
* A function that parse the http header.
*/
void parseHttpHeader(httpConnectionT *clientConnection) {
	char http[80], file[80], version[80], headerName[80], headerValue[120], *headerLine, *buffer;
	int ok, i = 0;
	
	buffer = initCharPointer(clientConnection->bufferSize);
	memcpy(buffer, clientConnection->buffer, clientConnection->bufferSize);

	ok = sscanf(buffer, "%s %s %s", http, file, version);
	if (ok) {
		headerLine = strtok(buffer, "\n");
		while (headerLine != NULL) {
			headerLine = strtok(NULL, "\r\n");
			if (headerLine != NULL) {
				sscanf(headerLine,"%s %[^\0]", headerName, headerValue);
				if (!strcmp(headerName, "If-Modified-Since:")) {
					printf("Conditional get!\n");
					strcpy(clientConnection->getConditionalDate, headerValue);
				}
			}
		}
		free(buffer);
		free(headerLine);
		strcpy(clientConnection->httpAction, http);
		strcpy(clientConnection->requestedFile, file);
		strcpy(clientConnection->protocolVersion, version);
		
		printf("Parsing complete\n");
	}
	else {
		printf("Parsing failed\n");
	}
}

/* Function: handleHttpConnection
* Usage: handleHttpConnection(conn);
* -------------------------------- 
* A function that handles the http connection.
*/
DWORD WINAPI handleHttpConnection(httpConnectionT *conn) {
	int result;
	fileInfoT fileInfo;
	responseHttpHeaderT responseHeader;

	fileInfo.contentSize = 0;
	fileInfo.fileBufferSize = 0;
	fileInfo.fileContent = NULL;

	result = httpRecieveLoop(conn);
	if (result != RECIEVE_OK)
		return 1;

	parseHttpHeader(conn);
	getCurrentTime(conn);

	result = getHttpContents(&fileInfo, conn);
	if (result != RESULT_OK) {
		switch (result) {
			case NO_CHANGE:			// 304
				printf("! Not changed\n");
				conn->statusCode = 304;
				break;
			case FILE_NOT_FOUND:	// 404
				printf("File %s not found\n", conn->requestedFile);
				conn->statusCode = 404;
				break;
			case STAT_ERROR:		// 500
				printf("! _stat error\n");
				conn->statusCode = 500;
				break;
			case CTIME_ERROR:		// 500
				printf("! ctime error\n");
				conn->statusCode = 500;
				break;
			default:				// 500
				printf("! unknown error\n");
				conn->statusCode = 500;
				break;
		}
	}
	else {
		conn->statusCode = 200;
	}
	
	responseHeader = generateHttpHeader(conn, &fileInfo);
	sendHttpHeader(conn, &responseHeader, fileInfo.contentSize);
	if (conn->statusCode == 200) {
		send(conn->clientSocket, fileInfo.fileContent, fileInfo.contentSize, 0);
	}
	
	free(fileInfo.fileContent);
	logConnection(conn);
	closeHttpConnection(conn);
	return 0;
}

/* Function: httpRecieveLoop
 * Usage: result = httpRecieveLoop(conn);
 * --------------------------------
 * This function recieves message from client and adds it to the connection buffer
 */
static int httpRecieveLoop(httpConnectionT* conn) {
	int dataSize = 0;

	while (1) {
		printf("\nReceiving...\n");
		dataSize = recv(conn->clientSocket, conn->buffer, BUFFER_SIZE, 0);

		if (dataSize == 0) {
			printf("Connection closed by client\n");
			return 0;
		}
		if (dataSize == SOCKET_ERROR) {
			printf("Socket error\n");
			return 0;
		}

		conn->bufferSize += (dataSize + 1);
		conn->buffer = allocateHttpBuffer(conn->buffer, conn->bufferSize);
		if (dataSize > 0 && dataSize != BUFFER_SIZE){
			break;
		}
	}
	conn->buffer[conn->bufferSize - 1] = 0;
	return RECIEVE_OK;
}

/* STATIC FUNCTIONS BELOW */

/* Function: initHttpBuffer
* Usage: newConn.buffer = initHttpBuffer();
* -------------------------------- 
* Initializes a buffer.  
*/
static char* initHttpBuffer() {
	char* buffer;

	buffer = (char*)malloc(BUFFER_SIZE * sizeof(char));
	return buffer;
}

/* Function: allocateHttpBuffer
* Usage: conn->buffer = allocateHttpBuffer(conn->buffer, conn->bufferSize);
* -------------------------------- 
* Allocate memory depending on the size of the buffer.
*/
static char* allocateHttpBuffer(char* buffer, const size_t size) {

	buffer = (char*) realloc(buffer, size * sizeof(char));
	return buffer;
}

/* Function: sendHttpHeader
* Usage: sendHttpHeader(conn, &responseHeader);
* -------------------------------- 
* A function that sends the information
* about the file to the header.
*/
static void sendHttpHeader(httpConnectionT *conn, responseHttpHeaderT *respHeader, int size) {
	char *fullMsg;
	
	fullMsg = initCharPointer(size + sizeof(responseHttpHeaderT));

	strcpy(fullMsg, respHeader->statusLine);
	strcat(fullMsg, "\r\n");
	addHttpHeaderToMsg(fullMsg, respHeader->connection);
	addHttpHeaderToMsg(fullMsg, respHeader->date);
	addHttpHeaderToMsg(fullMsg, respHeader->server);
	
	if(conn->statusCode == 200) {
		addHttpHeaderToMsg(fullMsg, respHeader->lastModified);
		addHttpHeaderToMsg(fullMsg, respHeader->contentLength);
		addHttpHeaderToMsg(fullMsg, respHeader->contentType);
	}
	strcat(fullMsg, "\r\n");

	//printf("Sending header:\n%s", fullMsg);
	send(conn->clientSocket, fullMsg, strlen(fullMsg), 0);
	free(fullMsg);
}


/* Function: addHttpHeaderToMsg
* Usage: addHttpHeaderToMsg(&fullMsg, &respHeader->connection); 
* -------------------------------- 
* Creates a messege from the header.
*/
static void addHttpHeaderToMsg(char* dest, char* source) {
	strcat(dest, source);
	if (source[strlen(source)-1] != '\n') {
		strcat(dest, "\n");
	}
}

/* Function: generateHttpHeader
* Usage: responseHeader = generateHttpHeader(conn, &fileInfo);
* -------------------------------- 
* Creates a http header.
*/
static responseHttpHeaderT generateHttpHeader(httpConnectionT *conn, fileInfoT* fileInfo) {
	responseHttpHeaderT respHeader;
	
	sprintf(respHeader.statusLine, "HTTP/1.1 %d %s", conn->statusCode, getHttpResponseFromCode(conn->statusCode));
	sprintf(respHeader.connection, "Connection: close");
	sprintf(respHeader.date, "Date: %s", conn->dateTime);
	sprintf(respHeader.server, "Server: %s", SERVER_NAME);
	
	if (conn->statusCode == 200) {
		sprintf(respHeader.lastModified, "Last-Modified: %s", fileInfo->lastModified);
		sprintf(respHeader.contentLength, "Content-Length: %d", fileInfo->contentSize);
		sprintf(respHeader.contentType, "Content-Type: %s", fileInfo->contentType);
	}
	return respHeader;
}

/* Function: getHttpResponseFromCode
* Usage:  getHttpResponseFromCode(conn->statusCode);
* -------------------------------- 
* Returns a message depending on what code 
* it gets.
*/
static char* getHttpResponseFromCode(int statusCode) {
	switch (statusCode) {
		case 200: return "OK";
		case 201: return "Created";
		case 204: return "No Content";
		case 301: return "Moved Permanently";
		case 302: return "Found";
		case 304: return "Not Modified";
		case 400: return "Bad Request";
		case 403: return "Forbidden";
		case 404: return "Not Found";
		case 405: return "Method Not Allowed";
		case 409: return "Conflict";
		case 411: return "Length Required";
		case 413: return "Request Entity Too Large";
		case 415: return "Unsupported Media Type";
		case 423: return "Locked";
		case 500: return "Server Error";
		case 501: return "Not Implemented";
		default: return "Server Error";
	}
}

/* Function: getCurrentTime
* Usage: getCurrentTime(conn);
* -------------------------------- 
* Gets the current time.
*/
static void getCurrentTime(httpConnectionT *conn) {
	time_t rawTime;
	struct tm* timeInfo;

	time(&rawTime);
	timeInfo = localtime(&rawTime);
	formatDate(conn->dateTime, timeInfo);
}

/* Function: closeHttpConnection
* Usage: closeHttpConnection(conn);
* -------------------------------- 
* Closes a http connection.
*/
static void closeHttpConnection(httpConnectionT *connection) {
	printf("Closing connection\n");
	free(connection->buffer);
	connectionClosedCallback(connection->connectionId);
	shutdown(connection->clientSocket, SD_SEND);
	closesocket(connection->clientSocket);
}

/* Function: formatDate
* Usage: formatDate(conn->dateTime, timeInfo);
* -------------------------------- 
* Returns the date in a correct format.
*/
errno_t formatDate(char* buf, struct tm *timeInfo) {
	return strftime(buf, 80, "%a, %d %b %Y %H:%M:%S GMT", timeInfo);
}